package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.ItemClient;

import com.leyou.common.constant.PageResult;
import com.leyou.common.exception.domain.ExceptionEnum;
import com.leyou.common.exception.domain.LyException;

import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;

import com.leyou.item.dto.*;
import com.leyou.search.domain.Goods;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.repository.SearchRepository;
import com.leyou.search.utils.HighlightUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Autowired
    private SearchRepository searchRepository;


    /**
     * 将一个Spu对象转成一个Goods对象
     */
    public Goods buildGoods(SpuDTO spuDTO){
        //根据spuId查询Sku集合
        List<SkuDTO> skuDTOS = itemClient.findSkusBySpuId(spuDTO.getId());
        //创建一个索引库中所需要的sku的字段的集合对象
        List<Map<String, Object>> indexSkus = new ArrayList<>();
        //遍历skuDTOS
        skuDTOS.forEach(skuDTO -> {
            Map<String, Object> indexSkuMap = new HashMap<>();
            indexSkuMap.put("id", skuDTO.getId());
            indexSkuMap.put("title", skuDTO.getTitle().substring(spuDTO.getName().length()));//只要除去SpuName之外的部分
            indexSkuMap.put("price", skuDTO.getPrice());
            indexSkuMap.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));//截取第一个逗号之前的部分
            //将indexSkuMap加入到indexSkus中
            indexSkus.add(indexSkuMap);
        });

        //得到所有价格的set集合
        Set<Long> priceSet = skuDTOS.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //提供一个存储动态规格参数集合的map
        Map<String, Object> specs = new HashMap<>();
        //得到map中key的集合【根据分类id和searching字段查询规格参数集合】
        List<SpecParamDTO> specParamDTOS = itemClient.findSpecParam(null, spuDTO.getCid3(), true);
        //得到map中value的来源【根据SpuId查询SpuDetail】
        SpuDetailDTO spuDetailDTO = itemClient.findSpuDetailBySpuId(spuDTO.getId());
        //得到通用规格参数的json格式字符串
        String genericSpecStr = spuDetailDTO.getGenericSpec();
        //将通用规格参数json格式字符串转成Map对象
        Map<Long, Object> genericSpecMap = JsonUtils.toMap(genericSpecStr, Long.class, Object.class);
        //得到特有规格参数的json格式字符串
        String specialSpecStr = spuDetailDTO.getSpecialSpec();
        //将特有规格参数json格式字符串转成Map对象
        Map<Long, List<Object>> specialSpecMap = JsonUtils.nativeRead(specialSpecStr, new TypeReference<Map<Long, List<Object>>>() {});
        //遍历规格参数集合【其实规格参数集合的长度就是最终动态规格参数map的长度】
        specParamDTOS.forEach(specParamDTO -> {
            //获取到动态规格参数的key值
            String key = specParamDTO.getName();
            Object value = null;
            //判断当前规格参数是否是通用规格参数
            if(specParamDTO.getGeneric()){
                value = genericSpecMap.get(specParamDTO.getId());
            }else {
                value = specialSpecMap.get(specParamDTO.getId());
            }
            //判断value是否是数字，如果是，兑换成区间再入索引库
            if(specParamDTO.getNumeric()){
                value = chooseSegment(value, specParamDTO);
            }
            specs.put(key, value);
        });

        //得到一个Goods对象
        Goods goods = new Goods();
        goods.setId(spuDTO.getId());
        goods.setSpuTitle(spuDTO.getName());//可以搜索，高亮字段
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setAll(spuDTO.getCategoryName()+spuDTO.getBrandName());//可以搜索
        goods.setSkus(JsonUtils.toString(indexSkus));
        goods.setPrice(priceSet);
        goods.setSpecs(specs);
        return goods;
    }

    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 搜索页面商品分页查询
     * @param request
     * @return
     */
    public PageResult<GoodsDTO> goodsPageQuery(SearchRequest request) {
        try {
            //提供一个可以封装复杂条件的对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //封装分页信息
            nativeSearchQueryBuilder.withPageable(PageRequest.of(request.getPage()-1, request.getSize()));
            //封装查询字段
            nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "spuTitle", "subTitle", "skus"}, null));
            //封装查询条件
            nativeSearchQueryBuilder.withQuery(getQueryBuilder(request));
            //指定高亮字段
            HighlightUtils.highlightField(nativeSearchQueryBuilder, "spuTitle");
            //索引库查询
            AggregatedPage<Goods> goodsPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(),
                    Goods.class,
                    HighlightUtils.highlightBody(Goods.class, "spuTitle"));

            PageResult<GoodsDTO> pageResult = new PageResult<>(goodsPage.getTotalElements(),
                    goodsPage.getTotalPages(),
                    BeanHelper.copyWithCollection(goodsPage.getContent(), GoodsDTO.class));
            return pageResult;
        }catch (Exception e){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }

    }

    //封装查询条件
    //封装查询条件
    private QueryBuilder getQueryBuilder(SearchRequest request) {
        //封装一个bool查询对象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //封装的条件查询
        boolQuery.must(QueryBuilders.multiMatchQuery(request.getKey(), "all", "spuTitle").operator(Operator.AND));
        //获取过滤条件的map
        Map<String, Object> paramMap = request.getParamMap();
        //遍历map
        paramMap.entrySet().forEach(entry->{
            //获取map的key
            String key = entry.getKey();
            //转成key的值
            if(key.equals("分类")){
                key = "categoryId";
            }else if (key.equals("品牌")){
                key = "brandId";
            }else {
                key = "specs."+key+".keyword";
            }
            //获取map的value
            Object value = entry.getValue();
            //封装过滤条件查询
            boolQuery.filter(QueryBuilders.termQuery(key, value));
        });
        //.operator(Operator.AND)被搜索的商品必须同时包含搜索条件中所有关键字才能匹配到。
        return boolQuery;
    }

    /**
     * 查询过滤条件
     * @param request
     * @return
     */
    public Map<String, List<?>> loadFilterParams(SearchRequest request) {
        //创建一个存储过滤条件的map
        Map<String, List<?>> map = new LinkedHashMap<>();
        //提供一个可以封装复杂条件的对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //封装分页信息
        nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));
        //封装查询条件
        nativeSearchQueryBuilder.withQuery(getQueryBuilder(request));
        //指定分类的聚合名称
        String categoryAgg = "categoryAgg";
        //添加分类的聚合条件
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        //指定品牌的聚合名称
        String brandAgg = "brandAgg";
        //添加分类的聚合条件
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));
        //查询索引库
        AggregatedPage<Goods> goodsAggResult = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
        //得到所有聚合的结果
        Aggregations aggregations = goodsAggResult.getAggregations();
        //得到分类的聚合结果
        Terms categoryTerms = aggregations.get(categoryAgg);
        //将分类的聚合结果封装到map中
        List<Long> categoryIds = handlerCategoryAggResult(map, categoryTerms);
        //得到品牌的聚合结果
        Terms brandTerms = aggregations.get(brandAgg);
        //将品牌的聚合结果封装到map中
        handlerBrandAggResult(map, brandTerms);
        //根据分类id的集合封装规格参数过滤条件
        handlerSpecParamFilterMap(map, categoryIds, getQueryBuilder(request));
        return map;
    }

    /**
     * 根据分类id的集合封装规格参数过滤条件
     * @param map
     * @param categoryIds
     * @param queryBuilder
     */
    private void handlerSpecParamFilterMap(Map<String, List<?>> map, List<Long> categoryIds, QueryBuilder queryBuilder) {
        //遍历分类id
        categoryIds.forEach(categoryId->{
            //提供一个可以封装复杂条件的对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //封装分页信息
            nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));
            //封装查询条件
            nativeSearchQueryBuilder.withQuery(queryBuilder);

            //根据分类id查询能过滤的规格参数
            List<SpecParamDTO> specParamDTOS = itemClient.findSpecParam(null, categoryId, true);
            //循环规格参数集合，把每一个规格参数都进行聚合
            specParamDTOS.forEach(specParamDTO -> {
                //当前规格参数的聚合名称
                String aggName = specParamDTO.getName();
                //当前规格参数的域名称
                String aggField = "specs."+specParamDTO.getName()+".keyword";
                //当前规格参数的聚合条件
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(aggName).field(aggField));
            });

            //查询索引库
            AggregatedPage<Goods> goodsAggResult = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
            //得到所有聚合的结果
            Aggregations aggregations = goodsAggResult.getAggregations();

            //循环规格参数集合，获取每一个聚合结果
            specParamDTOS.forEach(specParamDTO -> {
                //当前规格参数的聚合名称
                String aggName = specParamDTO.getName();
                //根据名称获取聚合结果
                Terms specTerms = aggregations.get(aggName);
                //得到当前规格参数聚合结果
                List<String> specValues = specTerms.getBuckets()
                        .stream()
                        .map(Terms.Bucket::getKeyAsString)
                        .collect(Collectors.toList());
                //将当前规格参数的聚合结果放入到结果map中
                map.put(aggName, specValues);
            });
        });
    }

    //将品牌的聚合结果封装到map中
    private void handlerBrandAggResult(Map<String, List<?>> map, Terms brandTerms) {
        List<BrandDTO> brandDTOS = brandTerms.getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber)//得到每一个桶中聚合结果的Number类型数据
                .map(Number::longValue)//将每一个Number的聚合结果转成Long
                .map(itemClient::findBrandById)//拿每一个Long类型id调用方法得到品牌对象
                .collect(Collectors.toList());
        map.put("品牌", brandDTOS);
    }

    //将分类的聚合结果封装到map中
    private List<Long> handlerCategoryAggResult(Map<String, List<?>> map, Terms categoryTerms) {
        List<Long> categoryIds = categoryTerms.getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        //调用接口方法获取分类对象集合
        List<CategoryDTO> categoryDTOS = itemClient.findByCategorysByIds(categoryIds);
        map.put("分类", categoryDTOS);
        return categoryIds;
    }


    /**
     * 搜索rabbitq
     * @param id
     */
    public void addIndex(Long id) {
        //得到一个SpuDTO对象
        SpuDTO spuDTO = itemClient.findSpuBySpuId(id);
        //通过SpuDTO得到一个Goods对象
        Goods goods = buildGoods(spuDTO);
        //添加索引库
        searchRepository.save(goods);
    }

    public void delIndex(Long id){
        searchRepository.deleteById(id);
    }



}



